/*
 * File:   Frequency.cpp
 * Author: Pete
 *
 * Created on June 2, 2012, 5:04 PM
 */

#include "frequency.h"
/**
 * How to use:
 * 1) Create new object (no parms for constructor)
 * 2) ->SetupBins(start, end, bucketSize)
 * 3) ->SetData(std::vector &data)
 * 4) ->reporting eg. ->Stddev, ->Average, ->Print, ->Dump
 * 5) ->Reset()
 * 6) - SetData and SetupBins again. etc.
 *
 */
/** \copydoc Frequency::Frequency */
Frequency::Frequency(void) {
	// Do nothing, parameters set up by this->setup
	// Must run setup before you can dump or report etc.
	_setupFlag = false;
	_setupBinsFlag = false;
	_setupDataFlag = false;
	_debugMode = false;
	_mapFreq.clear();
	_vecData.clear();
	// Initialise max/min
	// min set to max val, so first value always lower.
	_dataMin = DBL_MAX;
	// max set to lo val, so first sample value always higher.
	_dataMax = -DBL_MAX;
}
/**
 * Simple copy constructor by reference.
 * @param orig A Frequency object to copy.
 * @todo	Has not been tested, may need mods.
 */
Frequency::Frequency(const Frequency& orig) {
	if (&orig == this) {
		// both objects are pointing to same space in memory
		// ie they are the same.
	}
	else {
		//Explicity copy constructor items. Initialized a few things.
		_setupFlag = orig._setupFlag;
		_setupBinsFlag = orig._setupBinsFlag;
		_setupDataFlag = orig._setupDataFlag;
		_debugMode = false;
		_mapFreq.clear();
		_vecData.clear();
		this->_vecData = orig._vecData;
		this->_mapFreq = orig._mapFreq;
		this->_rankedZMap = orig._rankedZMap;
		this->_itData = orig._itData;
		this->_itMap = orig._itMap;

		_start = orig._start;
		_end = orig._end;
		_binSize = orig._binSize;
		_dataMin = orig._dataMin;
		_dataMax = orig._dataMax;
	}
}
/**
 * Add a vector of data samples to this frequency object.
 * This is the first method called. Reset removes data to start again with
 * new datasets/bins.
 * Must run setupBins first so addSample has a bin to allocate to.
 * @param parmData
 */
void Frequency::SetData(std::vector<double>& parmData) {
	// Process the supplied vector to do frequency analysis
	if (_setupBinsFlag!=true) {
		std::cerr << ERR_FREQ_BINS_NOT_SETUP << std::endl;
		return;
	}
	std::vector<double>::iterator dataIt;
	for (dataIt = parmData.begin(); dataIt!=parmData.end(); dataIt++) {
		this->AddSample(*dataIt);
	}
	_setupDataFlag = true;
}
/**
 * Run this method first (or after reset).Once bins are setup you can add data.
 * Note, to ensure map lookup works with double indexes, round all map bins to
 * 4 decimal places.
 * @param start The lowest bin value
 * @param end The highest bin value
 * @param bucketSize The size of the bins.
 */
void Frequency::SetBins(double start, double end, double binSize) {
	// Create the frequency map
	// e.g Put 10 pairs into the map, indexed 0-100 step 20
	// Create a bucket for values lower than the lowest bucket (start).
	_mapFreq.insert(std::pair<double,double>(-DBL_MAX,0));
	// Create the frequency bins based on start/end and using bucketSize to
	// create the range for a particular bucket.
	// Map key is double because can have decimal point indexes eg. 0.10 - 0.20
	// Warning : internal representation of double may be unrounded eg.
	// -1.899999999 instead of -1.9
	// Uses new round function to create bins properly.
	for (double i=start; i<=end ; i+=binSize) {
		double mapKey = this->_round(i,5);
		// Initialize the bucket frequency counts to zero.
		// A pair object is inserted into maps with a key and a value.
		_mapFreq.insert(std::pair<double,double>(mapKey,0));
	}
	// Create an extra bin for values higher than last bucket (end)
	_mapFreq.insert(std::pair<double,double>(DBL_MAX,0));
	// Update local properties. They get used by the add method.
	_binSize = binSize;
	// Set the object's start and end bins for labelling in Print();
	_start = start;
	_end = end;
	// OK to report
	_setupBinsFlag = true;
}



// Destructor - do nothing, no news.
Frequency::~Frequency() {
	_mapFreq.empty();
	_vecData.clear();
}

double Frequency::GetSample(int index) {
	if (!_setupDataFlag) {
		std::cerr << ERR_DATA_VECTOR_NOT_SETUP << std::endl;
		return 0.0;
	}
	if ((unsigned int)index>_vecData.size()) {
		std::cerr << ERR_DATA_INDEX_OUTSIDE_RANGE;
		std::cerr << "\t(0-" << _vecData.size() << ")" << std::endl;
	}
	return _vecData[index];
}
/**
 * Process sample by updating frequency bucket. Lo- and Hi-vals are added to
 * first or last bucket for values outside frequency distribution range.
 * @param addVal
 */
void Frequency::AddSample(double addVal) {
	if (_setupBinsFlag!=true) {
		std::cerr << ERR_FREQ_BINS_NOT_SETUP << std::endl;
		return;
	}
	// We have added at least one sample to data, set setupDataFlag.
	_setupDataFlag = true;
	// Add this sample value to main data vector
	this->_vecData.push_back(addVal);
	// Set min/max for this data set
	if (addVal>_dataMax) _dataMax = addVal;
	if (addVal<_dataMin) _dataMin = addVal;
	// Allocate it to a frequency bin.
	_allocateBin(addVal);
}

void Frequency::_allocateBin(double addVal) {
	// Allocate sample to frequency table.
	// Check outside start/end ranges for min bin and max bin
	// and increment these if required.
	// We exit function as soon as a bin has been allocated to
	// and confirm setupBinsFlag

	// Debug code
	if (addVal >= 100.0) {
		//std::cout << "Debug here";
	}
	// If value less than lowest specified bin value, add to lowest bin.
	if (addVal< _start) {
		// Add to -infinity bin
		_mapFreq.at(-DBL_MAX) +=1;
		this->_setupBinsFlag = true;
		return;
	}
	// If value greater than largest defined bucket + bucket size, put it in this catch all
	// for maximum values. Eg. last bin is 100, bin size is 20, check > 120. If > 120, put
	// in max bin.
	if (addVal > _end + _binSize ) {
		_mapFreq.at(DBL_MAX) += 1;
		this->_setupBinsFlag = true;
		return;
	}

	// We have a value within the defined bucket range, so update it
	// Go through the bin values

	for (_itMap = _mapFreq.begin() ; _itMap != _mapFreq.end(); ++_itMap ) {
		// Variables to look at frequency map
		// Now uses round function, as doubles cause problems eg.
		// Warning: adds twice to 2.2 and 2.3 buckets for 2.3 in a 0-5, 0.1 bin setup!
		// due to rounding errors of doubles (2.3 is 2.99999999998 etc.)
		//double bucketBegin = this->round((*itMap).first,2);
		addVal = this->_round(addVal,5);
		// Get current bucket value eg. 20.0
		double bucketBegin = (*_itMap).first;
		// If the sample >= the current bucket, and less than next bucket
		// eg. sample = 20.00, bucket 20.0-40.0, put in this bucket.
		if (addVal >= bucketBegin && addVal < (bucketBegin + _binSize)) {
			// Add one to the frequency count for this bucket.
			(*_itMap).second+=1;
			this->_setupBinsFlag = true;
			break;
		}
	}
	return;
}

void Frequency::DumpBins(void) {
	int x=1;
	for (_itMap = _mapFreq.begin() ; _itMap != _mapFreq.end(); ++_itMap ) {
		std::cout << "Bin #" << x << ":" << (*_itMap).first << std::endl;
		x++;
	}
	return;
}

std::vector<double> Frequency::GetBins(void) {
	std::vector<double> rtnVec;
	for (_itMap = _mapFreq.begin(); _itMap != _mapFreq.end(); ++_itMap ) {
		rtnVec.push_back((*_itMap).first);
	}
	return rtnVec;
}
std::vector<double>* Frequency::GetBinsPtr(void) {
	std::vector<double> *rtnVec = new std::vector<double>();
	for (_itMap = _mapFreq.begin(); _itMap != _mapFreq.end(); ++_itMap ) {
		rtnVec->push_back((*_itMap).first);
	}
	return rtnVec;
}
/*
 * Get highest value in sample data.
 */
double Frequency::Max(void) {
	return this->_dataMax;
}
/*
 * Get lowest value in sample data.
 */
double Frequency::Min(void) {
	return this->_dataMin;
}

/**
 * Use iterator to go through frequency map and output bucket and count
 */
void Frequency::Dump(void) {
	if (_setupDataFlag!=true) {
		std::cerr << ERR_DATA_VECTOR_NOT_SETUP << std::endl;
		return;
	}
	if (_setupBinsFlag!=true) {
		std::cerr << ERR_FREQ_BINS_NOT_SETUP << std::endl;
		return;
	}

	// show content:
	for ( _itMap=_mapFreq.begin() ; _itMap != _mapFreq.end(); ++_itMap ) {
		std::cout << (*_itMap).first << "\t" << (*_itMap).second <<  std::endl;
	}
	return;
}
/**
 * Return a map representing frequency table.
 * In pair, first is bin start, second is frequency value.
 * @return  std::map<double,double> A frequency table. Bins
 */
std::map<double,double>& Frequency::GetFrequencyTable(void) {
	if (_setupBinsFlag!=true || _setupDataFlag!=true) {
		std::cerr << ERR_NOT_SETUP << std::endl;
		return this->_mapFreq;
	}
	return this->_mapFreq;
}
/**
 * Get the frequency at a specified bin value
 * Data and bins must be set up, and bin must exist.
 */
double Frequency::GetFrequencyAtBin(double bin) {
	if (_setupBinsFlag!=true || _setupDataFlag!=true) {
		std::cerr << ERR_NOT_SETUP << std::endl;
		return 0.0;
	}
	// mapFreq is a std::map of <double, double>'s.
	// Check the bin exists in the frequency map
	if (_mapFreq.find(bin) != _mapFreq.end() ) {
		double freq = _mapFreq[bin];
		return freq;
	}
	// If the specified bin does not exist, report error.
	else {
		std::cerr << ERR_BIN_NOT_FOUND << " Bin was: " << bin << std::endl;
		return 0.0;
	}
	}
/**
 * Return number of samples in this dataset. This is the sum of all frequencies
 * allocated to the buckets. An alternative is to just return the size of vecData.
 * @return Number of samples (sum of all frequencies)
 */
int Frequency::N(void) {
	if (_setupBinsFlag != true || _setupDataFlag != true) {
		std::cerr << ERR_NOT_SETUP << std::endl;
		return 0;
	}
	// Return the total sample size of this frequency object.
	int numSamples = 0;
	// Add up the map's second (frequency)
	for ( _itMap=_mapFreq.begin() ; _itMap != _mapFreq.end(); ++_itMap ) {
		numSamples += (*_itMap).second;
	}
	return numSamples;
}

void Frequency::PrintTree(void) {
	if (_setupBinsFlag != true || _setupDataFlag != true) {
		std::cerr << ERR_NOT_SETUP << std::endl;
		return;
	}
	// 1. Work out how many columns required
	int colsIndex = 0;
	double maxFreq = 0;
	double minFreq = 0;

	// Find maximum in table
	// eg. 0, 5, 30, 20, 10, 7, 0
	for ( _itMap=_mapFreq.begin() ; _itMap != _mapFreq.end(); ++_itMap ) {
		if ((*_itMap).second > maxFreq) maxFreq = (*_itMap).second;
		if ((*_itMap).second < minFreq) minFreq = (*_itMap).second;
	}
	// Eg max is 30, min is 0
	// Work out how many columns per frequency.
	std::cout << "Frequency Tree" << std::endl;
	std::cout << "--------------" << std::endl;
	// Work out how much each star represents
	// If range < 60, each star = 1
	// If range > 60, work out divisor eg. range = 130, int(range)/60 = 2
	// so each star represents 2
	colsIndex = (maxFreq - minFreq) / 60 +1; // eg max freq = 200, then colsIndex = 2.4 = 2 ie. each
	// Now iterate through map, printing *'s per colsIndex eg. for 30, print 30,
	int mapSize = _mapFreq.size();
	int count = 0;
	int colWidth = 15;
	for ( _itMap=_mapFreq.begin() ; _itMap != _mapFreq.end(); ++_itMap ) {
		count++;
		// For printing, give the lo and hi val labels something more meaningul
		// Low bin = '<xxx'
		if (count == 1) std::cout << std::left <<   "<" << std::left << std::setw(colWidth) << this->_start << "\t";
		// High bin = >xxx
		if (count == mapSize) std::cout << std::left <<  ">" << std::setw(colWidth) << this->_end << "\t";
		if (_itMap!=_mapFreq.begin() && count != mapSize) {
			std::cout << std::setw(colWidth) << std::left << (*_itMap).first << std::string(colWidth-4, ' ');
		}
		int starsToPrint = _itMap->second / colsIndex; // eg. 60 /2 = 30
		for (int x=0; x < starsToPrint; x++) {
			std::cout << "*";
		}
		std::cout << std::endl;
	}
}
/**
 * Reset ie. clear data vector and frequency map, to start again.
 */
void Frequency::Reset(void) {
	_mapFreq.clear();
	_vecData.clear();
}
/**
 * Reset the data vector, but leave the bins alone.
 */
void Frequency::ResetData(void) {
	_vecData.clear();
}
// Calculated standard deviation. Does not use boost.
double Frequency::StdDev(void) {
	if (_setupBinsFlag!=true || _setupDataFlag!=true) {
		std::cerr << ERR_NOT_SETUP << std::endl;
		return 0.0;
	}
	double n = this->_vecData.size();
	double mean = this->Average();
	double s; // Holds final value to look at
	double sumvar = 0.0;
	// Sum of the squared variances
	for (int i=0; i<n; i++) {
		sumvar += pow((_vecData[i]-mean),2);
	}
	// Divide sum of squared variances by number of samples-1 (sample n)
	sumvar *= (1/(n-1));
	// The square root of average sum of squared variances is the standard
	// deviation.
	s = pow(sumvar,0.5);
	return s;
}

double Frequency::Average(void) {
	if (_setupBinsFlag!=true || _setupDataFlag!=true) {
		std::cerr << ERR_NOT_SETUP << std::endl;
		return 0.0;
	}
	return (this->Sum()/_vecData.size());
}

double Frequency::Sum(void) {
	if (_setupBinsFlag!=true || _setupDataFlag!=true) {
		std::cerr << ERR_NOT_SETUP << std::endl;
		return 0.0;
	}
	double sampleSum = 0;
	// Can't use mapFreq because they are binned and not accurate.
	for (_itData = _vecData.begin(); _itData!= _vecData.end(); _itData++ ) {
		sampleSum += *_itData;
	}
	return sampleSum;
}
/**
 * Generic round to x decimal places function
 * From: http://stackoverflow.com/questions/485525/round-for-float-in-c
 * What we want is rounding to x places ie. -22000 rounds to -22000
 * -23000.3423443 to -23000.34234.
 * Calculation:
 * Say we want rounded to 3 decimal places ie. -23000.3423
 * Could turn into a string, chop off unwanted decimal points and round up/down.
 * OR
 *
 * DOES NOT WORK.
 * @param number
 * @param place
 * @return
 */
// Looks like it works
double Frequency::_round3( double x, int places )
{
	const double sd = pow(10,places);
	// -22000 * 100000 + (if x <0, -0.5 else +0.5)/100000
	//double rtnVal = int(x*sd + (x<0? -0.5 : 0.5))/sd;
	// Could also use 'nearbyint' (double nearbyint(double)).

	double rtnVal = floorf(x * sd + 0.5) / sd;

	return rtnVal;
}

double Frequency::_round2(double x, int places) {
	// eg. 26.308984 to 2 decimal places = 26.39.
	// 308984/100 = 308.984
	// round(x) rounds to nearest integer.
	// round(308.984) = 309. 309 is mantissa.
	// Pain in the arse but:
	//
	// double mantissa = x-int(x); // = 0.308984
	// s = strlen(mantissa) -2 // eg. 8 - 2 = 6
	// s1 = substr(mantissa,2,6) = 308984
	// s2 = round(30.8984); // = 31
	// s3 = int(x) + 31/100; // =26 + (31 / pow(10,2)) = 26.31;
	float rtnVal = floorf(x * pow(10,places) + 0.5) / (pow(10,places));
	return rtnVal;

}
/* Function to control # of decimal places to be output for x */
double Frequency::_round(const double& x, const int& numDecimals) {
    int y=x;
    double z=x-y;
    double m=pow(10,numDecimals);
    double q=z*m;
    double r=round(q);
    return static_cast<double>(y)+(1.0/m)*r;
}